﻿#define TRACE

using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Design;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Xml.Linq;
using EnvDTE;
using log4net;
using Microsoft.VisualStudio.Project;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.Win32;
using PowerGui.AddOn;
using PowerGui.AddOn.Interfaces;
using PowerGuiVsx.Interfaces;
using PowerGuiVsx.Module;
using PowerGUIVsx.Project;
using QuestSoftware.PowerGUIExt;
using QuestSoftware.PowerGUIVsx;
using MessageBox = System.Windows.Forms.MessageBox;

namespace PowerGUIVsx
{
    /// <summary>
    /// This is the class that implements the package exposed by this assembly.
    ///
    /// The minimum requirement for a class to be considered a valid package for Visual Studio
    /// is to implement the IVsPackage interface and register itself with the shell.
    /// This package uses the helper classes defined inside the Managed Package Framework (MPF)
    /// to do it: it derives from the Package class that provides the implementation of the 
    /// IVsPackage interface and uses the registration attributes defined in the framework to 
    /// register itself and its components with the shell.
    /// </summary>
    // This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is
    // a package.
    [PackageRegistration(UseManagedResourcesOnly = true)]
    // This attribute is used to register the informations needed to show the this package
    // in the Help/About dialog of Visual Studio.
    [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
    // This attribute is needed to let the shell know that this package exposes some menus.
    [ProvideMenuResource("Menus.ctmenu", 1)]
    [ProvideEditorExtension(typeof(EditorFactory), ".ps1", 50, 
              ProjectGuid = "{A2FE74E1-B743-11d0-AE1A-00A0C90FFFC3}", 
              TemplateDir = "Templates", 
              NameResourceID = 105, 
              DefaultName = "PowerShell Script Editor")]
    [ProvideEditorExtension(typeof(EditorFactory), ".psd1", 50,
          ProjectGuid = "{A2FE74E1-B743-11d0-AE1A-00A0C90FFFC3}",
          TemplateDir = "Templates",
          NameResourceID = 105,
          DefaultName = "PowerShell Script Editor")]
    [ProvideEditorExtension(typeof(EditorFactory), ".psm1", 50,
          ProjectGuid = "{A2FE74E1-B743-11d0-AE1A-00A0C90FFFC3}",
          TemplateDir = "Templates",
          NameResourceID = 105,
          DefaultName = "PowerShell Script Editor")]
    [ProvideKeyBindingTable(GuidList.guidPowerGUIExtEditorFactoryString, 102)]
    [ProvideEditorLogicalView(typeof(EditorFactory), "{7651a703-06e5-11d1-8ebd-00a0c90f26ea}")]
    [Guid(GuidList.guidPowerGUIExtPkgString)]
    //Engine Registration Information
    [ProvideDebugEngine("{43ACAB74-8226-4920-B489-BFCF05372437}", "PowerShell", PortSupplier = "{708C1ECA-FF48-11D2-904F-00C04FA302A1}", ProgramProvider = "{08F3B557-C153-4F6C-8745-227439E55E79}", Attach = true, CLSID = "{C7F9F131-53AB-4FD0-8517-E54D124EA392}")]
    [Clsid(Clsid = "{C7F9F131-53AB-4FD0-8517-E54D124EA392}", Assembly = "PowerGuiVsx.Core.DebugEngine", Class=  "PowerGuiVsx.Core.DebugEngine.Engine")]
    [Clsid(Clsid = "{08F3B557-C153-4F6C-8745-227439E55E79}", Assembly = "PowerGuiVsx.Core.DebugEngine", Class = "PowerGuiVsx.Core.DebugEngine.ScriptProgramProvider")]
    [ProvideIncompatibleEngineInfo("{92EF0900-2251-11D2-B72E-0000F87572EF}")]
    //[ProvideIncompatibleEngineInfo("{449EC4CC-30D2-4032-9256-EE18EB41B62B}")]
    //[ProvideIncompatibleEngineInfo("{449EC4CC-30D2-4032-9256-EE18EB41B62B}")]
    [ProvideIncompatibleEngineInfo("{F200A7E7-DEA5-11D0-B854-00A0244A1DE2}")]
    [ProvideToolWindow(typeof(InputOutputToolWindow))]
    [ProvideProjectFactory(typeof(PowerShellProjectFactory), "PowerShell", "PowerShell Project Files (*.pssproj);*.pssproj", "pssproj", "pssproj", @"\Templates\ProjectTemplates\PowerShell", LanguageVsTemplate = "PowerShell", NewProjectRequireNewFolderVsTemplate = false)]
    [ProvideProjectItem(typeof(PowerShellProjectFactory), "PowerShell", @"Templates", 500)]
    [ProvideObject(typeof(PowerShellProjectPropertyPage))]
    [ProvideObject(typeof(PowerShellModulePropertyPage))]
    [Export(typeof(IPowerGUIVsxPackage))]
    public sealed class PowerGuiVsxPackage : ProjectPackage, IPowerGUIVsxPackage
    {
        #region Fields

        private readonly static ILog Log = LogManager.GetLogger(typeof(PowerGuiVsxPackage));

        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets the install path of PowerGUI.
        /// </summary>
        /// <value>The install path.</value>
        public string InstallPath { get; set; }

        public IEditorFactory EditorFactory { get; set; }

        [Import]
        public IPowerGui PowerGui { get; set; }

        public override string ProductUserContext
        {
            get { return "PowerShellProj"; }
        }
        #endregion

        public PowerGuiVsxPackage()
        {
            var fileInfo =
                new FileInfo(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
                                          "PowerGuiVsxLogConfig.xml"));

            if (fileInfo.Exists)
            {
                log4net.Config.XmlConfigurator.Configure(fileInfo);    
                Log.Info("Logging enabled...");
            }
        }

        #region Methods

        /// <summary>
        /// This function is called when the user clicks the menu item that shows the 
        /// tool window. See the Initialize method to see how the menu item is associated to 
        /// this function using the OleMenuCommandService service and the MenuCommand class.
        /// </summary>
        private void ShowToolWindow(object sender, EventArgs e)
        {
            // Get the instance number 0 of this tool window. This window is single instance so this instance
            // is actually the only one.
            // The last flag is set to true so that if the tool window does not exists it will be created.
            ToolWindowPane window = this.FindToolWindow(typeof(InputOutputToolWindow), 0, true);
            if ((null == window) || (null == window.Frame))
            {
                throw new NotSupportedException("Cannot create window!");
            }

            IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame;
            Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show());
        }

        /// <summary>
        /// Initialization of the package; this method is called right after the package is sited, so this is the place
        /// where you can put all the initilaization code that rely on services provided by VisualStudio.
        /// </summary>
        protected override void Initialize()
        {
            Log.Debug("Entering Initialize()");

            base.Initialize();

            

            try
            {
                PowerGuiComposer.Compose(this);
                PowerGui.CommandKeyMappings = new VisualStudioCommandKeyMappings();
            }
            catch (PowerGuiInstallationException ex)
            {
                Log.Error("Failed to load PowerGUI VSX assemblies.", ex);
                    MessageBox.Show(
                    ex.Message,
                    "PowerGUI VSX", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            RegisterEngine();
            RegisterProjectFactory(new PowerShellProjectFactory(this));

            try
            {
                PowerGui.Initialize();
                InstallPath = PowerGui.InstallPath;

                var dte = (DTE) GetService(typeof (DTE));
                PowerGui.Debugger.SetVariable("EnvDte", dte);

                PowerGuiVsxCommand.Intialize(this);

                var assemblyUri = new Uri(Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase));
                PowerGui.Debugger.ExecuteText("Import-Module \"" + Path.Combine(assemblyUri.LocalPath, "PowerGUIVSX.Module.psd1") + "\"");


            }
            catch (Exception ex)
            {
                Log.Error("Failed to load PowerGUI configuration settings.", ex);
                MessageBox.Show(
                                "Unable to load PowerGUI components. Please install PowerGUI before using this extensions",
                                "Error Loading PowerGUI Components",
                                MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            EditorFactory = new EditorFactory(this);
            PowerGuiComposer.Compose(EditorFactory, false);
            RegisterEditorFactory(EditorFactory as EditorFactory);

            var mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
            if (null != mcs)
            {
                // Create the command for the tool window
                var toolwndCommandID = new CommandID(GuidList.guidPowerGUIExtCmdSet, PkgCmdIDList.icmdOpenIoWindow);
                var menuToolWin = new MenuCommand(ShowToolWindow, toolwndCommandID);
                mcs.AddCommand(menuToolWin);
            }

            var window = FindToolWindow(typeof(InputOutputToolWindow), 0, true) as InputOutputToolWindow;

            Log.Debug("Leaving Initialize()");

        }

        public object VsDebugger
        { 
            get; private set;
        }

        public new object GetService(Type type)
        {
            return base.GetService(type);
        }

        public TK GetService<T, TK>() where TK : class
        {
            return base.GetService(typeof(T)) as TK;
        }

        public void RegisterEngine()
        {
            Log.Debug("Entering RegisterEngine().");
            RegistryKey key = null;
            try
            {
                key = ApplicationRegistryRoot.OpenSubKey(@"CLSID\{C7F9F131-53AB-4FD0-8517-E54D124EA392}");

                if (key == null)
                {
                    Log.Error("The debug engine was not installed correctly.");
                    ShowErrorMessageBox("The debug engine was not installed correctly.");
                    return;
                }

                Log.DebugFormat("PowerGUI VSX Registration Key [{0}].", key.ToString());

                var assemblyLocation = GetType().Assembly.Location;
               
                if (String.IsNullOrEmpty(assemblyLocation) || !File.Exists(assemblyLocation))
                {
                    Log.Error("Could not properly locate the PowerGUI VSX assemblies.");
                    throw new ApplicationException("Failed to identitfy the current assembly's file path.");
                }

                var location = new FileInfo(assemblyLocation).Directory;
                Log.DebugFormat("PowerGUI VSX file location [{0}].", location);

                if (location == null)
                {
                    throw new ApplicationException("Failed to identitfy the current assembly's file path.");
                }

                var currentPath = Path.Combine(location.FullName, "PowerGuiVsx.Core.DebugEngine.dll");

                if (!Registered(key, currentPath))
                {
                    Log.Info("Debug engine is not registered. Registering debug engine.");
                    string stringKey;
                    using (key)
                    {
                        stringKey = key.ToString();
                    }
                    
                    if (stringKey.ToUpper().StartsWith("HKEY_LOCAL_MACHINE"))
                    {
                        stringKey = stringKey.Remove(0, "HKEY_LOCAL_MACHINE\\".Length);
                        key = Registry.LocalMachine.OpenSubKey(stringKey, true);
                    }
                    else if (stringKey.ToUpper().StartsWith("HKEY_CURRENT_USER"))
                    {
                        stringKey = stringKey.Remove(0, "HKEY_CURRENT_USER\\".Length);
                        key = Registry.CurrentUser.OpenSubKey(stringKey, true);
                    }

                    if (key == null)
                    {
                        Log.Error("Debug engine was not registered correctly.");
                        ShowErrorMessageBox("The debug engine was not installed correctly.");
                        return;
                    }

                    Log.DebugFormat("Setting Codebase value to [{0}].", currentPath);
                    key.SetValue("Codebase", currentPath);

                    StreamReader streamReader = null;
                    String xmlContent;

                    try
                    {
                        var configFile = Path.Combine(location.FullName, "PowerGuiVsx.Core.DebugEngine.dll.Config");
                        streamReader = new StreamReader(File.OpenRead(configFile));
                        Log.DebugFormat("Opening assembly configuration file [{0}].", configFile);
                        xmlContent = streamReader.ReadToEnd();
                    }
                    finally
                    {
                        if (streamReader != null)
                        {
                            streamReader.Close();
                            streamReader.Dispose();
                        }
                    }

                    if (String.IsNullOrEmpty(xmlContent))
                    {
                        Log.Error("Assembly configuration file was empty. Could not update PowerGUI assembly locations.");
                        ShowErrorMessageBox("Failed to read the App.Config file.");
                        return;
                    }
                    
                    Log.Debug("Parsing XML file and setting PowerGUI assembly locations.");
                    var document = XDocument.Parse(xmlContent);
                    var dependAsses = document.Descendants("dependentAssembly");
                    foreach (var xnode in dependAsses)
                    {
                        var assemblyNode = xnode.Descendants("assemblyIdentity").FirstOrDefault();
                        if (assemblyNode == null)
                        {
                            continue;
                        }

                        var nameAttrib = assemblyNode.Attribute("name");
                        if (nameAttrib == null)
                        {
                            continue;
                        }

                        var assemblyPath = Path.Combine(PowerGui.InstallPath, nameAttrib.Value + ".dll");
                        var assembly = Assembly.LoadFrom(assemblyPath);
                        var version = assembly.GetName().Version;
                        Log.InfoFormat("Updating codebase location of [{0}] with version [{1}].", assembly, version);
                        xnode.Add(
                            new XElement("codeBase",
                                new XAttribute("href", assemblyPath),
                                new XAttribute("version", version.ToString(4))
                                ));
                    }

                    var configFile2 = Path.Combine(location.FullName, "PowerGuiVsx.Core.DebugEngine.dll.Config");
                    Log.InfoFormat("Saving assembly configuration file to [{0}].", configFile2);
                    document.Save(configFile2);
                }
            }
            catch (Exception ex)
            {
                Log.Error("Failed to update assembly configuration file.", ex);
                ShowErrorMessageBox(ex.Message);
            }
            finally
            {
                if (key != null)
                {
                    key.Close();    
                    key.Dispose();
                }
            }
        }

        public void ShowErrorMessageBox(string error)
        {
            MessageBox.Show(String.Format(Resources.FailedToInstallDebugEngine, error), Resources.FailedToInstallDebugEngineTitle , MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

        private bool Registered(RegistryKey key, string destFile)
        {
            var value = key.GetValue("CodeBase");

            return value == null ? false : value.Equals(destFile);
        }



        #endregion
    }
}
